// Some configuration values are complex strings - so we need parsers/generators for them.
Ext.define('PVE.Parser', {
    statics: {
        // this class only contains static functions

        printACME: function (value) {
            if (Ext.isArray(value.domains)) {
                value.domains = value.domains.join(';');
            }
            return PVE.Parser.printPropertyString(value);
        },

        parseACME: function (value) {
            if (!value) {
                return {};
            }

            let res = {};
            try {
                value.split(',').forEach((property) => {
                    let [k, v] = property.split('=', 2);
                    if (Ext.isDefined(v)) {
                        res[k] = v;
                    } else {
                        throw `Failed to parse key-value pair: ${property}`;
                    }
                });
            } catch (err) {
                console.warn(err);
                return undefined;
            }

            if (res.domains !== undefined) {
                res.domains = res.domains.split(/;/);
            }

            return res;
        },

        parseBoolean: function (value, default_value) {
            if (!Ext.isDefined(value)) {
                return default_value;
            }
            value = value.toLowerCase();
            return value === '1' || value === 'on' || value === 'yes' || value === 'true';
        },

        parsePropertyString: function (value, defaultKey) {
            let res = {};

            if (typeof value !== 'string' || value === '') {
                return res;
            }

            try {
                value.split(',').forEach((property) => {
                    let [k, v] = property.split('=', 2);
                    if (Ext.isDefined(v)) {
                        res[k] = v;
                    } else if (Ext.isDefined(defaultKey)) {
                        if (Ext.isDefined(res[defaultKey])) {
                            throw 'defaultKey may be only defined once in propertyString';
                        }
                        res[defaultKey] = k; // k is the value in this case
                    } else {
                        throw `Failed to parse key-value pair: ${property}`;
                    }
                });
            } catch (err) {
                console.warn(err);
                return undefined;
            }

            return res;
        },

        printPropertyString: function (data, defaultKey) {
            var stringparts = [],
                gotDefaultKeyVal = false,
                defaultKeyVal;

            Ext.Object.each(data, function (key, value) {
                if (defaultKey !== undefined && key === defaultKey) {
                    gotDefaultKeyVal = true;
                    defaultKeyVal = value;
                } else if (value !== '') {
                    stringparts.push(key + '=' + value);
                }
            });

            stringparts = stringparts.sort();
            if (gotDefaultKeyVal) {
                stringparts.unshift(defaultKeyVal);
            }

            return stringparts.join(',');
        },

        parseQemuNetwork: function (key, value) {
            if (!(key && value)) {
                return undefined;
            }

            let res = {},
                errors = false;
            Ext.Array.each(value.split(','), function (p) {
                if (!p || p.match(/^\s*$/)) {
                    return undefined; // continue
                }

                let match_res;

                if (
                    (match_res = p.match(
                        /^(ne2k_pci|e1000e?|e1000-82540em|e1000-82544gc|e1000-82545em|vmxnet3|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i,
                    )) !== null
                ) {
                    res.model = match_res[1].toLowerCase();
                    if (match_res[3]) {
                        res.macaddr = match_res[3];
                    }
                } else if ((match_res = p.match(/^bridge=(\S+)$/)) !== null) {
                    res.bridge = match_res[1];
                } else if ((match_res = p.match(/^rate=(\d+(\.\d+)?|\.\d+)$/)) !== null) {
                    res.rate = match_res[1];
                } else if ((match_res = p.match(/^tag=(\d+(\.\d+)?)$/)) !== null) {
                    res.tag = match_res[1];
                } else if ((match_res = p.match(/^firewall=(\d+)$/)) !== null) {
                    res.firewall = match_res[1];
                } else if ((match_res = p.match(/^link_down=(\d+)$/)) !== null) {
                    res.disconnect = match_res[1];
                } else if ((match_res = p.match(/^queues=(\d+)$/)) !== null) {
                    res.queues = match_res[1];
                } else if (
                    (match_res = p.match(/^trunks=(\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*)$/)) !== null
                ) {
                    res.trunks = match_res[1];
                } else if ((match_res = p.match(/^mtu=(\d+)$/)) !== null) {
                    res.mtu = match_res[1];
                } else {
                    errors = true;
                    return false; // break
                }
                return undefined; // continue
            });

            if (errors || !res.model) {
                return undefined;
            }

            return res;
        },

        printQemuNetwork: function (net) {
            var netstr = net.model;
            if (net.macaddr) {
                netstr += '=' + net.macaddr;
            }
            if (net.bridge) {
                netstr += ',bridge=' + net.bridge;
                if (net.tag) {
                    netstr += ',tag=' + net.tag;
                }
                if (net.firewall) {
                    netstr += ',firewall=' + net.firewall;
                }
            }
            if (net.rate) {
                netstr += ',rate=' + net.rate;
            }
            if (net.queues) {
                netstr += ',queues=' + net.queues;
            }
            if (net.disconnect) {
                netstr += ',link_down=' + net.disconnect;
            }
            if (net.trunks) {
                netstr += ',trunks=' + net.trunks;
            }
            if (net.mtu) {
                netstr += ',mtu=' + net.mtu;
            }
            return netstr;
        },

        parseQemuDrive: function (key, value) {
            if (!(key && value)) {
                return undefined;
            }

            const [, bus, index] = key.match(/^([a-z]+)(\d+)$/);
            if (!bus) {
                return undefined;
            }
            let res = {
                interface: bus,
                index,
            };

            var errors = false;
            Ext.Array.each(value.split(','), function (p) {
                if (!p || p.match(/^\s*$/)) {
                    return undefined; // continue
                }
                let match = p.match(/^([a-z_]+)=(\S+)$/);
                if (!match) {
                    if (!p.match(/[=]/)) {
                        res.file = p;
                        return undefined; // continue
                    }
                    errors = true;
                    return false; // break
                }
                let [, k, v] = match;
                if (k === 'volume') {
                    k = 'file';
                }

                if (Ext.isDefined(res[k])) {
                    errors = true;
                    return false; // break
                }

                if (k === 'cache' && v === 'off') {
                    v = 'none';
                }

                res[k] = v;

                return undefined; // continue
            });

            if (errors || !res.file) {
                return undefined;
            }

            return res;
        },

        printQemuDrive: function (drive) {
            var drivestr = drive.file;

            Ext.Object.each(drive, function (key, value) {
                if (
                    !Ext.isDefined(value) ||
                    key === 'file' ||
                    key === 'index' ||
                    key === 'interface'
                ) {
                    return; // continue
                }
                drivestr += ',' + key + '=' + value;
            });

            return drivestr;
        },

        parseIPConfig: function (key, value) {
            if (!(key && value)) {
                return undefined; // continue
            }

            let res = {};
            try {
                value.split(',').forEach((p) => {
                    if (!p || p.match(/^\s*$/)) {
                        return; // continue
                    }

                    const match = p.match(/^(ip|gw|ip6|gw6)=(\S+)$/);
                    if (!match) {
                        throw `could not parse as IP config: ${p}`;
                    }
                    let [, k, v] = match;
                    res[k] = v;
                });
            } catch (err) {
                console.warn(err);
                return undefined; // continue
            }

            return res;
        },

        printIPConfig: function (cfg) {
            return Object.entries(cfg)
                .filter(([k, v]) => v && k.match(/^(ip|gw|ip6|gw6)$/))
                .map(([k, v]) => `${k}=${v}`)
                .join(',');
        },

        parseLxcNetwork: function (value) {
            if (!value) {
                return undefined;
            }

            let data = {};
            value.split(',').forEach((p) => {
                if (!p || p.match(/^\s*$/)) {
                    return; // continue
                }
                let match_res = p.match(/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|tag|rate)=(\S+)$/);
                if (match_res) {
                    data[match_res[1]] = match_res[2];
                } else if ((match_res = p.match(/^firewall=(\d+)$/)) !== null) {
                    data.firewall = PVE.Parser.parseBoolean(match_res[1]);
                } else if ((match_res = p.match(/^link_down=(\d+)$/)) !== null) {
                    data.link_down = PVE.Parser.parseBoolean(match_res[1]);
                } else if (!p.match(/^type=\S+$/)) {
                    console.warn(`could not parse LXC network string ${p}`);
                }
            });

            return data;
        },

        printLxcNetwork: function (config) {
            let knownKeys = {
                bridge: 1,
                firewall: 1,
                gw6: 1,
                gw: 1,
                hwaddr: 1,
                ip6: 1,
                ip: 1,
                mtu: 1,
                name: 1,
                rate: 1,
                tag: 1,
                link_down: 1,
            };
            return Object.entries(config)
                .filter(([k, v]) => v !== undefined && v !== '' && knownKeys[k])
                .map(([k, v]) => `${k}=${v}`)
                .join(',');
        },

        parseLxcMountPoint: function (value) {
            if (!value) {
                return undefined;
            }

            let res = {};
            let errors = false;
            Ext.Array.each(value.split(','), function (p) {
                if (!p || p.match(/^\s*$/)) {
                    return undefined; // continue
                }
                let match = p.match(/^([a-z_]+)=(.+)$/);
                if (!match) {
                    if (!p.match(/[=]/)) {
                        res.file = p;
                        return undefined; // continue
                    }
                    errors = true;
                    return false; // break
                }
                let [, k, v] = match;
                if (k === 'volume') {
                    k = 'file';
                }

                if (Ext.isDefined(res[k])) {
                    errors = true;
                    return false; // break
                }

                res[k] = v;

                return undefined;
            });

            if (errors || !res.file) {
                return undefined;
            }

            const match = res.file.match(/^([a-z][a-z0-9\-_.]*[a-z0-9]):/i);
            if (match) {
                res.storage = match[1];
                res.type = 'volume';
            } else if (res.file.match(/^\/dev\//)) {
                res.type = 'device';
            } else {
                res.type = 'bind';
            }

            return res;
        },

        printLxcMountPoint: function (mp) {
            let drivestr = mp.file;
            for (const [key, value] of Object.entries(mp)) {
                if (
                    !Ext.isDefined(value) ||
                    key === 'file' ||
                    key === 'type' ||
                    key === 'storage'
                ) {
                    continue;
                }
                drivestr += `,${key}=${value}`;
            }
            return drivestr;
        },

        parseStartup: function (value) {
            if (value === undefined) {
                return undefined;
            }

            let res = {};
            try {
                value.split(',').forEach((p) => {
                    if (!p || p.match(/^\s*$/)) {
                        return; // continue
                    }

                    let match_res;
                    if ((match_res = p.match(/^(order)?=(\d+)$/)) !== null) {
                        res.order = match_res[2];
                    } else if ((match_res = p.match(/^up=(\d+)$/)) !== null) {
                        res.up = match_res[1];
                    } else if ((match_res = p.match(/^down=(\d+)$/)) !== null) {
                        res.down = match_res[1];
                    } else {
                        throw `could not parse startup config ${p}`;
                    }
                });
            } catch (err) {
                console.warn(err);
                return undefined;
            }

            return res;
        },

        printStartup: function (startup) {
            let arr = [];
            if (startup.order !== undefined && startup.order !== '') {
                arr.push('order=' + startup.order);
            }
            if (startup.up !== undefined && startup.up !== '') {
                arr.push('up=' + startup.up);
            }
            if (startup.down !== undefined && startup.down !== '') {
                arr.push('down=' + startup.down);
            }

            return arr.join(',');
        },

        parseQemuSmbios1: function (value) {
            let res = value.split(',').reduce((acc, currentValue) => {
                const [k, v] = currentValue.split(/[=](.+)/);
                acc[k] = v;
                return acc;
            }, {});

            if (PVE.Parser.parseBoolean(res.base64, false)) {
                for (const [k, v] of Object.entries(res)) {
                    if (k !== 'uuid') {
                        res[k] = Ext.util.Base64.decode(v);
                    }
                }
            }

            return res;
        },

        printQemuSmbios1: function (data) {
            let base64 = false;
            let datastr = Object.entries(data)
                .map(([key, value]) => {
                    if (value === '') {
                        return undefined;
                    }
                    if (key !== 'uuid') {
                        base64 = true; // smbios values can be arbitrary, so encode and mark config as such
                        value = Ext.util.Base64.encode(value);
                    }
                    return `${key}=${value}`;
                })
                .filter((v) => v !== undefined)
                .join(',');

            if (base64) {
                datastr += ',base64=1';
            }
            return datastr;
        },

        parseTfaConfig: function (value) {
            let res = {};
            value.split(',').forEach((p) => {
                const [k, v] = p.split('=', 2);
                res[k] = v;
            });

            return res;
        },

        parseTfaType: function (value) {
            let match;
            if (!value || !value.length) {
                return undefined;
            } else if (value === 'x!oath') {
                return 'totp';
            } else if ((match = value.match(/^x!(.+)$/)) !== null) {
                return match[1];
            } else {
                return 1;
            }
        },

        parseQemuCpu: function (value) {
            if (!value) {
                return {};
            }

            let res = {};
            let errors = false;
            Ext.Array.each(value.split(','), function (p) {
                if (!p || p.match(/^\s*$/)) {
                    return undefined; // continue
                }

                if (!p.match(/[=]/)) {
                    if (Ext.isDefined(res.cpu)) {
                        errors = true;
                        return false; // break
                    }
                    res.cputype = p;
                    return undefined; // continue
                }

                let match = p.match(/^([a-z_-]+)=(\S+)$/);
                if (!match || Ext.isDefined(res[match[1]])) {
                    errors = true;
                    return false; // break
                }

                let [, k, v] = match;
                res[k] = v;

                return undefined;
            });

            if (errors || !res.cputype) {
                return undefined;
            }

            return res;
        },

        printQemuCpu: function (cpu) {
            let cpustr = cpu.cputype;
            let optstr = '';

            Ext.Object.each(cpu, function (key, value) {
                if (!Ext.isDefined(value) || key === 'cputype') {
                    return; // continue
                }
                optstr += ',' + key + '=' + value;
            });

            if (!cpustr) {
                if (optstr) {
                    return 'kvm64' + optstr;
                } else {
                    return undefined;
                }
            }

            return cpustr + optstr;
        },

        parseSSHKey: function (key) {
            //                |--- options can have quotes--|     type    key        comment
            let keyre = /^(?:((?:[^\s"]|"(?:\\.|[^"\\])*")+)\s+)?(\S+)\s+(\S+)(?:\s+(.*))?$/;
            let typere =
                /^(?:(?:sk-)?(?:ssh-(?:dss|rsa|ed25519)|ecdsa-sha2-nistp\d+)(?:@(?:[a-z0-9_-]+\.)+[a-z]{2,})?)$/;

            let m = key.match(keyre);
            if (!m || m.length < 3 || !m[2]) {
                // [2] is always either type or key
                return null;
            }
            if (m[1] && m[1].match(typere)) {
                return {
                    type: m[1],
                    key: m[2],
                    comment: m[3],
                };
            }
            if (m[2].match(typere)) {
                return {
                    options: m[1],
                    type: m[2],
                    key: m[3],
                    comment: m[4],
                };
            }
            return null;
        },

        parseACMEPluginData: function (data) {
            let res = {};
            let extradata = [];
            data.split('\n').forEach((line) => {
                // capture everything after the first = as value
                let [key, value] = line.split(/[=](.+)/);
                if (value !== undefined) {
                    res[key] = value;
                } else {
                    extradata.push(line);
                }
            });
            return [res, extradata];
        },

        filterPropertyStringList: function (list, filterFn, defaultKey) {
            return list.filter((entry) =>
                filterFn(PVE.Parser.parsePropertyString(entry, defaultKey)),
            );
        },
    },
});
